import sys
import socket, asyncore
import struct
import threading
import broker_protocol_pb2 as bp

class View:

    def __init__(self, vid, primary, cohorts):
        self.vid = vid
        self.primary = primary
        self.cohorts = cohorts

class Plugin(asyncore.dispatcher_with_send):
    
    def __init__(self, target, callback=None):
        self.callback = callback
        self.first_time = True
        self.stable = True

        asyncore.dispatcher_with_send.__init__(self)
        try:
            self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self.connect('/tmp/cvmfs_broker-%d' % target)
            # read the welcome message from the broker
        except Exception, e:
            print "Error in connecting to broker: %s" % e
            sys.exit(1)

        self.wlock = threading.Lock()
        self.view_lock = threading.Lock()
        self.io_thread = threading.Thread(target = asyncore.loop)
        self.io_thread.start()

    ###########################
    # asyncore implementation #
    ###########################

    def handle_connect(self):
        pass # suppress the warning

    def handle_read(self):
        msg_size = self.recv(4)
        if len(msg_size) < 4:
            return
        # read 4-byte size header
        msg_size = int(struct.unpack("!i", msg_size)[0])
        msg = self.recv(msg_size)

        if self.first_time:
            # read the welcome message
            welcome = bp.ViewMessage()
            welcome.ParseFromString(msg)
            self.view_lock.acquire()
            self.broker_id = welcome.me
            self.view = View(welcome.newvid, welcome.primary, welcome.cohorts)
            self.first_time = False
            self.view_change()
            print "connected to broker %s (vid %d)" % (self.broker_id, \
                self.view.vid)
            self.view_lock.release()
        else:
            imsg = bp.DataMessage()
            imsg.ParseFromString(msg)
            if imsg.src == '':
                # an internal message from the broker
                if imsg.subject == 'PREVC':
                    # an ongoing view change
                    self.view_lock.acquire()
                    self.stable = False
                    self.view_lock.release()
                elif imsg.subject == 'POSTVC':
                    vmsg = bp.ViewMessage()
                    vmsg.ParseFromString(imsg.body)
                    self.view_lock.acquire()
                    # update member list and vid
                    # print "new view: %d primary %s" % (vmsg.newvid,\
                    #         vmsg.primary)
                    self.view.vid = vmsg.newvid
                    self.view.primary = vmsg.primary
                    self.view.cohorts = vmsg.cohorts
                    self.stable = True

                    self.view_change()
                    self.view_lock.release()
            else:
                self.handle_msg(imsg.src, imsg.subject, imsg.body)

    def handle_close(self):
        self.close()

    ###########################
    #    public interfaces    #
    ###########################

    def subscribe(self, subject, errcallback = None):
        # subscribe interested subjects so that i can receiver further messages
        sub_msg = bp.MessageWrapper()
        sub_msg.mtype = bp.MessageWrapper.SUB
        sub_msg.subject = subject
        self._send(sub_msg)

    def get_broker_id(self):
        return self.broker_id

    def get_view(self):
        """
        return the current participants as a tuple, with the primary being
        the first
        """
        v = None
        self.view_lock.acquire()
        v = self.view
        self.view_lock.release()
        return v

    def unicast(self, dest, subject, body):
        unicast_msg = bp.MessageWrapper()
        unicast_msg.mtype = bp.MessageWrapper.DATA
        unicast_msg.data.dest.append(dest)
        unicast_msg.data.subject = subject
        unicast_msg.data.body = body
        self._send(unicast_msg)

    def broadcast(self, subject, body):
        bcast_msg = bp.MessageWrapper()
        bcast_msg.mtype = bp.MessageWrapper.DATA
        bcast_msg.data.subject = subject
        bcast_msg.data.body = body
        self._send(bcast_msg)

    #def invoke(self, plugin_id, func, *args):
    #"""Invoke a specified method of another local plugin"""
    #    pass

    def handle_msg(self, src, subject, body):
        """Subclasses should override this method to define actions regarding
           an incoming message"""
        print 'warning: unhandled message event'

    def view_change(self):
        # implementors could overwrite this method to handle view change events
        pass

    def _send(self, pb_msg):
        self.wlock.acquire()
        to_send = struct.pack('!i', pb_msg.ByteSize()) + \
                pb_msg.SerializeToString()
        self.send(to_send)
        self.wlock.release()

